/**
 * 
 */
package il.ac.bgu.cs.heuristics.graph;

import il.ac.bgu.cs.heuristics.graph.pddl.PddlUtils;
import il.ac.bgu.cs.heuristics.graph.pddl.SafeCast;
import il.ac.bgu.cs.heuristics.graph.pddl.auxiliary.ContradictionResolver;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.Action;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.BoundParameter;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.ITypedConstant;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.Predicate;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.Type;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;
import il.ac.bgu.cs.heuristics.utils.MathUtils;
import il.ac.bgu.cs.heuristics.utils.Validation;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import pddl4j.Domain;
import pddl4j.Problem;

/**
 * A store for definitions and operations needed to create an
 * {@link ActionGraph}
 * 
 * @author Ronen Pinko
 * 
 */
public final class GraphBuildEssentials {
	/** domain predicates */
	private final Set<Predicate>									predicates		= CollectionUtils
																							.newLinkedHashSet();

	/** problems constants */
	private final Set<ITypedConstant>								constants		= CollectionUtils
																							.newLinkedHashSet();

	/** domain actions */
	private final Set<Action>										actions			= CollectionUtils
																							.newLinkedHashSet();

	/** maps predicate name to {@link Predicate} */
	private final Map<String, Predicate>							nameToPredicate	= new TreeMap<String, Predicate>(
																							String.CASE_INSENSITIVE_ORDER);

	/** generates permutations for constants */
	private final MathUtils.PermutationResultCache<ITypedConstant>	permutationResultCache;

	/** handles contradiction resolution */
	private final ContradictionResolver								contradictionResolver;

	private final Domain											domain;

	private final Problem											problem;

	private final Map<Type, List<Type>>								typesHierarchy;

	private GraphBuildEssentials(Domain domain, Problem problem,
			ContradictionResolver contradictionResolver) {
		final Set<Type> types = PddlUtils.extractTypeSet(domain);
		this.typesHierarchy = createTypeHierarchy(types);
		this.domain = domain;
		this.problem = problem;
		this.predicates.addAll(PddlUtils.extractPredicates(domain,
				typesHierarchy));
		this.constants.addAll(PddlUtils.extractTypedConstants(domain, problem,
				types));
		this.permutationResultCache = new MathUtils.PermutationResultCache<ITypedConstant>();
		this.actions.addAll(PddlUtils.extractActions(domain));
		for (Predicate predicate : predicates) {
			nameToPredicate.put(predicate.getName(), predicate);
		}
		this.contradictionResolver = contradictionResolver;
	}

	public static Map<Type, List<Type>> createTypeHierarchy(Set<Type> types) {
		Map<Type, List<Type>> map = CollectionUtils.newHashMap();
		List<Type> list = null;
		for (Type type : types) {
			list = map.get(type.parent());
			if (list == null) {
				list = CollectionUtils.newArrayList();
				map.put(type.parent(), list);
			}
			list.add(type);
		}
		return map;
	}

	public static GraphBuildEssentials createInstance(File domainFile,
			File problemFile, InputStream contradictionStream)
			throws IllegalArgumentException, IOException {
		final Domain domain = PddlUtils.compilePDDL(Validation
				.ensureArgumentNeverNull(domainFile));
		final Problem problem = PddlUtils.compilePDDL(Validation
				.ensureArgumentNeverNull(problemFile));
		Validation.ensureArgumentNeverNull(contradictionStream);
		Validation.ensureArgumentNeverNull(domain);
		Validation.ensureArgumentNeverNull(problem);
		PddlUtils.validatePddl(domain, problem);
		final ContradictionResolver resolver = new ContradictionResolver(
				contradictionStream);
		return new GraphBuildEssentials(domain, problem, resolver);
	}

	public Set<Predicate> getPredicates() {
		return Collections.unmodifiableSet(predicates);
	}

	/**
	 * May return {@code null}.
	 */
	public ITypedConstant getConstantByName(String name) {
		for (ITypedConstant constant : constants) {
			final BoundParameter param = SafeCast.toBoundParameter(constant);
			if (param.name().equalsIgnoreCase(name)) {
				return param;
			}
		}
		return null;
	}

	public Set<Action> getActions() {
		return Collections.unmodifiableSet(actions);
	}

	public ContradictionResolver getResolver() {
		return contradictionResolver;
	}

	public Domain getDomain() {
		return domain;
	}

	public Problem getProblem() {
		return problem;
	}

	public Map<String, Predicate> getNameToPredicate() {
		return Collections.unmodifiableMap(nameToPredicate);
	}

	public Iterable<List<ITypedConstant>> getConstantsPermutations(int size) {
		return permutationResultCache.createKPermutations(constants, size);
	}

}